package com.xishu.service;

import com.xishu.bo.LockOrderCashOrOrderItemBo;
import com.xishu.config.Config;
import com.xishu.entity.LockOrderCashOrOrderItem;
import com.xishu.entity.order.Order;
import com.xishu.entity.order.OrderItem;
import com.xishu.entity.order.PartCash;
import com.xishu.exception.ResponseStatusException;
import com.xishu.response.ResponseStatus;
import com.xishu.response.VerifyUtil;
import com.xishu.service.order.base.OrderService;
import com.xishu.util.ServletUtil;
import com.xishu.util.Tools;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static com.xishu.config.Constant.*;
import static com.xishu.response.ResponseStatus.WAITER_OPERATE_THE_ORDER;

/**
 * 锁分两种，一种是支付方式的锁
 * PayTypeLock lockSessionId lockTime
 * 一种是分菜，分钱的锁
 * LockOrderCashOrOrderItem
 */
public class PartPayService implements Tools {
    private static Logger logger = LoggerFactory.getLogger(PartPayService.class);
    private static PartPayService instance = new PartPayService();
    private CommonService commonService = CommonService.getInstance();
    private LockService localLockService = LockService.getInstance();

    public static PartPayService getInstance() {
        return instance;
    }

    private PartPayService() {
    }

    /**
     * 给分钱结账分配钱
     * 并发由外层控制
     *
     * @param order
     * @return
     */
    public Double assignPartPersonCashInB(Order order) throws ResponseStatusException {
        VerifyUtil.verify(() -> order != null);
        VerifyUtil.verify(() -> order.getPartCash() != null);
        return order.getUserCost();
//        PartCash partCash = order.getPartCash();
//        //几个人分钱结账
//        Integer persons = order.getPartCash().getPersons();
//        //平均每个人多少
//        double avgCost = order.getUserCost() / persons;
//        logger.info("avg cost is {}, persons {}", avgCost, persons);
//
//        //如果付款人数还大于2，那么继续均分
//        Integer payedNumber = getInt(partCash.getPayedNumber());
//        logger.info("part pay json is {}", partCash);
//
//        if (persons == payedNumber) {
//            logger.info("all payed, will return");
//            VerifyUtil.throwError(ResponseStatus.ORDER_EXPIRED);
//        }
//
//        if (persons - payedNumber >= 2) {
//            double perCost = showMoneyDouble(partCash.getPerCost());
//            logger.info("perCost is {}", perCost);
//            return perCost;
//        }
//
//        //最后一个人付款了，那么直接剩下所有的钱
//        double leftCash = showMoneyDouble(order.getLeftCash());
//        logger.info("leftCash is {}", leftCash);
//        return leftCash;
    }

    /**
     * C端分钱
     *
     * @param orderInDb
     * @return
     */
    public Double assignPartPersonCashInC(Order orderInDb) throws Exception {
        VerifyUtil.verify(() -> orderInDb != null);
        VerifyUtil.verify(() -> orderInDb.getPartCash() != null);
        PartCash partCash = orderInDb.getPartCash();

        List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemList = filterExpireOrder(orderInDb);

        //到这里已经是合法的锁定量了
        //先判断是否已经在锁定的列表里面了
        String sessionId = ServletUtil.getSessionId();
        Optional<LockOrderCashOrOrderItem> lockCashOptional = lockOrderCashOrOrderItemList.stream().filter(p -> equals(p.getLockSessionId(), sessionId) && !getBoolean(p.getPayed())).findFirst();

        if (lockCashOptional.isPresent()) {
            //时间延长
            LockOrderCashOrOrderItem lockCash = lockCashOptional.get();
            lockCash.setLockTime(System.currentTimeMillis());
            commonService.save(orderInDb);
            Double cash = lockCashOptional.get().getCash();
            logger.info("already lock the cash {}", cash);
            return cash;
        }

        //没有找到，那么分配一个,并且保存入库
        //如果付款人数还大于2，那么继续均分
        Integer persons = partCash.getPersons();

        Integer lockedNumber = lockOrderCashOrOrderItemList.size();
        double cost = 0d;

        if (persons - lockedNumber >= 2) {
            cost = showMoneyDouble(partCash.getPerCost());
            logger.info("perCost is {}", cost);
        } else if (persons - lockedNumber == 1) {
            logger.info("user cost is {}", showDouble(orderInDb.getUserCost()));
            double sumLockCash = lockOrderCashOrOrderItemList.stream().mapToDouble(p -> getDouble(p.getCash())).sum();
            //最后一个人付款了，那么直接剩下所有的钱
            cost = showMoneyDouble(orderInDb.getUserCost() - sumLockCash);
            logger.info("leftCash is {}", cost);
        } else {
            //超了，那么报错
            VerifyUtil.throwError(ResponseStatus.OTHER_USER_OPERATE_THE_ORDER);
        }

        LockOrderCashOrOrderItem lockOrderCashOrOrderItem = new LockOrderCashOrOrderItem();
        lockOrderCashOrOrderItem.setLockSessionId(sessionId);
        lockOrderCashOrOrderItem.setLockTime(System.currentTimeMillis());
        lockOrderCashOrOrderItem.setLockCash(true);
        lockOrderCashOrOrderItem.setCash(cost);
        lockOrderCashOrOrderItemList.add(lockOrderCashOrOrderItem);
        commonService.save(orderInDb);

        return cost;
    }

    /**
     * 过滤掉锁定失效的客户端
     *
     * @param orderInDb
     * @return
     */
    public List<LockOrderCashOrOrderItem> filterExpireOrder(Order orderInDb) {
        //先清空过滤的锁定量
        long currentTimeMillis = System.currentTimeMillis();
        List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemList = getList(orderInDb.getLockOrderCashOrOrderItemList());
        lockOrderCashOrOrderItemList = lockOrderCashOrOrderItemList.stream().filter(p -> {
            //已经支付的保留
            if (getBoolean(p.getPayed())) {
                return true;
            }

            long diffTime = currentTimeMillis - p.getLockTime();
            //没有过期，并且是未支付的锁定量
            return diffTime <= Config.getInstance().getLockCashTime() && !getBoolean(p.getPayed());
        }).collect(Collectors.toList());

        orderInDb.setLockOrderCashOrOrderItemList(lockOrderCashOrOrderItemList);
        return lockOrderCashOrOrderItemList;
    }

    /**
     * 在C端锁定菜品
     * <p>
     * 同样的菜，在浏览器上面也是传的是相同的ID，哪怕是有多个菜的情况
     *
     * @param lockOrderCashOrOrderItemBo
     */
    public List<Long> lockOrderItemInC(Order orderInDb, LockOrderCashOrOrderItemBo lockOrderCashOrOrderItemBo) {
        List<Long> orderItemIdList = getList(lockOrderCashOrOrderItemBo.getOrderItemIdList());

        if (isEmpty(orderItemIdList)) {
            logger.info("orderItemIdList is null");
            return new ArrayList<>();
        }

        List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemListInDb = getList(orderInDb.getLockOrderCashOrOrderItemList());
        String sessionId = ServletUtil.getSessionId();
        //已支付的锁定量，或者是其它人的锁定量
        lockOrderCashOrOrderItemListInDb = lockOrderCashOrOrderItemListInDb.stream().filter(p -> !equals(p.getLockSessionId(), sessionId) || getBoolean(p.getPayed())).collect(Collectors.toList());

        //算出每一个菜品的锁定量
        Map<Long, Long> lockCountMap = calOrderItemLockCount(lockOrderCashOrOrderItemListInDb);
        ArrayList<Long> otherUserLockOrderItemIdList = new ArrayList<>();

        //计算这次要锁定的菜的数量
        Map<Long, List<Long>> thisTimeLockCount = orderItemIdList.stream().collect(Collectors.groupingBy(p -> p.longValue()));

        for (Long orderItemId : orderItemIdList) {
            int thisLockCount = thisTimeLockCount.get(orderItemId).size();
            //总共的数量
            OrderItem orderItem = OrderService.getInstance().filterOrderItemList(orderInDb, orderItemId);
            if (thisLockCount + lockCountMap.getOrDefault(orderItemId, 0l) > orderItem.getCount()) {
                logger.info("order item not enough {}", orderItemId);
                otherUserLockOrderItemIdList.add(orderItemId);
            }
        }

        return otherUserLockOrderItemIdList;
    }

    /**
     * 算出每一个菜的锁定量
     *
     * @param lockOrderCashOrOrderItemListInDb
     * @return
     */
    public Map<Long, Long> calOrderItemLockCount(List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemListInDb) {
        HashMap<Long, Long> lockMap = new HashMap<>();

        for (LockOrderCashOrOrderItem lockOrderCashOrOrderItem : lockOrderCashOrOrderItemListInDb) {
            logger.info("the lock order item is {}", lockOrderCashOrOrderItem);
            for (Long orderItemId : lockOrderCashOrOrderItem.getOrderItemIdList()) {
                Long count = lockMap.get(orderItemId);
                if (count == null) {
                    count = 0l;
                }

                count = count + 1;
                lockMap.put(orderItemId, count);
            }
        }

        logger.info("the lock map is {}", StringUtils.join(lockMap.entrySet(), ","));
        return lockMap;
    }

    /**
     * 释放锁定量
     *
     * @param order
     */
    public void releaseLock(Order order) throws Exception {
        String sessionId = ServletUtil.getSessionId();
        List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemList = getList(order.getLockOrderCashOrOrderItemList());
        lockOrderCashOrOrderItemList = lockOrderCashOrOrderItemList.stream().filter(p -> !equals(p.getLockSessionId(), sessionId)).collect(Collectors.toList());
        order.setLockOrderCashOrOrderItemList(lockOrderCashOrOrderItemList);
        commonService.save(order);
    }

    /**
     * 锁自定义现金
     *
     * @param lockOrderCashOrOrderItemBo
     * @return
     */
    public Double lockTheCustomCash(LockOrderCashOrOrderItemBo lockOrderCashOrOrderItemBo, Order orderInDb) throws Exception {
        Long userId = ServletUtil.getUserId();
        if (userId != null) {
            logger.info("its b end");
            return 0d;
        }

        //先过滤掉已经过期的锁
        filterExpireOrder(orderInDb);

        if (orderInDb.getUserSelectPayType() == null) {
            logger.info("will set the user select pay type {} to part cash customized");
            orderInDb.setUserSelectPayType(USER_SELECT_PAY_TYPE_PART_CASH_CUSTOMIZED);
        }

        if (orderInDb.getStatus() < ORDER_STATUS_PART_CASH_CUSTOMIZED) {
            logger.info("set the order status to {}", ORDER_STATUS_PART_CASH_CUSTOMIZED);
            orderInDb.setStatus(ORDER_STATUS_PART_CASH_CUSTOMIZED);
        }

        VerifyUtil.verify(() -> getInt(orderInDb.getUserSelectPayType()) == USER_SELECT_PAY_TYPE_PART_CASH_CUSTOMIZED, ResponseStatus.ORDER_TIME_OUT);
        List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemList = getList(orderInDb.getLockOrderCashOrOrderItemList());
        double nowLockCash = lockOrderCashOrOrderItemList.stream().mapToDouble(p -> showDouble(p.getCash())).sum();
        logger.info("now lock the cash is {}", nowLockCash);

        String sessionId = ServletUtil.getSessionId();
        Optional<LockOrderCashOrOrderItem> cashOrOrderItemOptional = lockOrderCashOrOrderItemList.stream().filter(p -> equals(sessionId, p.getLockSessionId())).findAny();

        if (cashOrOrderItemOptional.isPresent()) {
            LockOrderCashOrOrderItem lockOrderCashOrOrderItem = cashOrOrderItemOptional.get();
            logger.info("before lock cash {}", lockOrderCashOrOrderItem.getCash());
            lockOrderCashOrOrderItem.setCash(showDouble(lockOrderCashOrOrderItemBo.getCash()));
            lockOrderCashOrOrderItem.setLockTime(System.currentTimeMillis());
        } else {
            LockOrderCashOrOrderItem lockOrderCashOrOrderItem = new LockOrderCashOrOrderItem();
            lockOrderCashOrOrderItem.setLockSessionId(sessionId);
            lockOrderCashOrOrderItem.setCash(showDouble(lockOrderCashOrOrderItemBo.getCash()));
            lockOrderCashOrOrderItem.setLockCash(true);
            lockOrderCashOrOrderItem.setLockTime(System.currentTimeMillis());
            lockOrderCashOrOrderItemList.add(lockOrderCashOrOrderItem);
        }

        //判断金额是否足够
        double lockCash = lockOrderCashOrOrderItemList.stream().mapToDouble(p -> getDouble(p.getCash())).sum();
        double canInputNumber = showDouble(orderInDb.getUserCost() - nowLockCash);
        logger.info("now lock the total cash is {}, userCost {}, other lock the cash {}, canInputNumber {}", lockCash, orderInDb.getUserCost(), nowLockCash, canInputNumber);
        VerifyUtil.verify(() -> lockCash <= orderInDb.getUserCost(), ResponseStatus.ONLY_CAN_PAY, canInputNumber);
        logger.info("lock the custom cash ok");
        orderInDb.setLockOrderCashOrOrderItemList(lockOrderCashOrOrderItemList);
        commonService.save(orderInDb);
        return lockOrderCashOrOrderItemBo.getCash();
    }

    /**
     * 一个人锁定订单
     *
     * @param lockOrderCashOrOrderItemBo
     * @param orderInDb
     * @return
     */
    public void oneUserPayLock(LockOrderCashOrOrderItemBo lockOrderCashOrOrderItemBo, Order orderInDb) throws Exception {
        Long userId = ServletUtil.getUserId();
        if (userId != null) {
            logger.info("its b end");
            return;
        }

        //先过滤掉已经过期的锁
        filterExpireOrder(orderInDb);

        //要么是第一次进来，要么多次进来，但是必须是选中的一个人付款
        VerifyUtil.verify(() -> getInt(orderInDb.getUserSelectPayType()) == USER_SELECT_PAY_TYPE_ONE_USER || orderInDb.getUserSelectPayType() == null, ResponseStatus.ORDER_TIME_OUT);

        List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemList = getList(orderInDb.getLockOrderCashOrOrderItemList());
        orderInDb.setLockOrderCashOrOrderItemList(lockOrderCashOrOrderItemList);

        String sessionId = ServletUtil.getSessionId();
        Optional<LockOrderCashOrOrderItem> cashOrOrderItemOptional = lockOrderCashOrOrderItemList.stream().filter(p -> equals(sessionId, p.getLockSessionId())).findAny();

        if (cashOrOrderItemOptional.isPresent()) {
            LockOrderCashOrOrderItem lockOrderCashOrOrderItem = cashOrOrderItemOptional.get();
            lockOrderCashOrOrderItem.setCash(showDouble(lockOrderCashOrOrderItemBo.getCash()));
            lockOrderCashOrOrderItem.setLockTime(System.currentTimeMillis());
            logger.info("same session id");
        } else {
            LockOrderCashOrOrderItem lockOrderCashOrOrderItem = new LockOrderCashOrOrderItem();
            lockOrderCashOrOrderItem.setCash(showDouble(lockOrderCashOrOrderItemBo.getCash()));
            lockOrderCashOrOrderItem.setLockSessionId(sessionId);
            lockOrderCashOrOrderItem.setLockTime(System.currentTimeMillis());
            lockOrderCashOrOrderItemList.clear();
            lockOrderCashOrOrderItemList.add(lockOrderCashOrOrderItem);
            logger.info("diff session id");
        }

        logger.info("lockOrderCashOrOrderItemList is {}", StringUtils.join(lockOrderCashOrOrderItemList, ","));

        //最多只能有一个用户锁定，并且必须是当前用户
        boolean validLock = lockOrderCashOrOrderItemList.size() == 1 && lockOrderCashOrOrderItemList.stream().filter(p -> equals(p.getLockSessionId(), sessionId)).findAny().isPresent();
        VerifyUtil.verify(() -> validLock, ResponseStatus.ORDER_TIME_OUT);

        //保存订单
        logger.info("save order in one user lock");
        commonService.save(orderInDb);
    }

    /**
     * 添加锁定时间
     */
    public void addLockTime(Long shopId, Long orderId, int payType) throws Exception {
        Long userId = ServletUtil.getUserId();

        if (userId != null) {
            logger.info("its b end");
            return;
        }

        OrderService orderService = OrderService.getInstance();
        String lockKey = orderService.getLockKey(shopId, orderId);
        Object lock = localLockService.getLock(lockKey);

        //加锁添加时间
        synchronized (lock) {
            Order orderInDb = orderService.findOrder(shopId, orderId);

            VerifyUtil.verify(() -> !getBoolean(orderInDb.getLockByWaiter()), WAITER_OPERATE_THE_ORDER);

            List<LockOrderCashOrOrderItem> lockOrderCashOrOrderItemList = getList(orderInDb.getLockOrderCashOrOrderItemList());
            String sessionId = ServletUtil.getSessionId();
            List<LockOrderCashOrOrderItem> sameSessionIdList = lockOrderCashOrOrderItemList.stream().filter(p -> equals(p.getLockSessionId(), sessionId) && !getBoolean(p.getPayed())).collect(Collectors.toList());

            //如果没有找到，那么报错
            VerifyUtil.verify(() -> isNotEmpty(sameSessionIdList), ResponseStatus.ORDER_TIME_OUT);

            for (LockOrderCashOrOrderItem lockOrderCashOrOrderItem : sameSessionIdList) {
                lockOrderCashOrOrderItem.setLockTime(System.currentTimeMillis());
                lockOrderCashOrOrderItem.setPayType(payType);
            }

            //保存
            commonService.save(orderInDb);
        }
    }
}
